// const axios = require("axios")
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const docPath = "D:\\intelliJ\\CangjieProject\\docs\\cjnative"
const targetDir = "cangjie_libs"

const config={

}

const cjLibs = [
  {
    name: "std",
    pkgs: ["core", "argopt", "ast", "binary", "collection", "collection.concurrent", "console", 
      "convert", "crypto.digest", "crypto.cipher", "database.sql", "deriving", "ffi.python", 
      "format", "fs", "io", "log", "math", "math.numeric", "net", "objectpool", "os", 
      "os.posix", "os.process", "overflow", "random", "reflect", "regex", "runtime", "socket", 
      "sort", "sync", "time", "unicode", "unittest", "unittest.mock", "unittest.mockmacro", 
      "unittest.testmacro", "unittest.common", "unittest.diff", "unittest.prop_test"
    ]
  },
  {
    name: "compress",
    pkgs: ["zlib"]
  },
  {
    name: "crypto",
    pkgs: ["crypto", "digest", "keys", "x509"]
  },
  {
    name: "encoding",
    pkgs: ["base64", "hex", "json", "json.stream", "url", "xml"]
  },
  {
    name: "fuzz",
    pkgs: ["fuzz"]
  },
  {
    name: "log",
    pkgs: ["log"]
  },
  {
    name: "logger",
    pkgs: ["logger"]
  },
  {
    name: "net",
    pkgs: ["http", "tls"]
  },
  {
    name: "serialization",
    pkgs: ["serialization"]
  }
]

function findDirsIncludes(dir, ...extensions) {
  const files = fs.readdirSync(dir);
  const matchingFiles = files.filter(file => {
    const filePath = path.join(dir, file);
    return fs.statSync(filePath).isDirectory() && extensions.every(ext => file.includes(ext));
  });
  return matchingFiles.map(file => path.join(dir, file));
}

function generateCJDoc(intros, params, return_, throws, hasIndent = false) {
  const indent = () => hasIndent ? "\t" : ""
  result = indent() + "/**\n"
  if (intros.length > 0) {
    for(let intro of intros) {
      result += indent() + " * " + intro + "\n"
    }
  }
  if (params.length > 0) {
    for(let param of params) {
      result += indent() + " * @param " + param + "\n"
    }
  }
  if (return_) {
    result += indent() + " * @return " + return_ + "\n"
  }
  if (throws) {
    result += indent() + " * @throws " + throws + "\n"
  }
  if (intros.length > 0 || params.length > 0 || return_ || throws) {
    return result + indent() + "*/"
  } else {
    return null
  }
}

function nextSibling($, elem, tagName) {
  let n = $(elem).next()
  while(n.length > 0 && $(n).prop("tagName").toLowerCase() != tagName) {
    n = n.next()
  }
  return n
}

if (!fs.existsSync(`./${targetDir}`)) {
  fs.mkdirSync(path.join(`./${targetDir}`), { recursive: true });
}

for (lib of cjLibs) {
  for (pkg of lib.pkgs) {
    let libPath;
    let pkgName = pkg.replace(/\./g, "_");
    if (lib.name === "std") {
      if (pkg.startsWith("crypto")) {
        libPath = findDirsIncludes(path.join(docPath, "libs", lib.name, pkg.split(".")[0], pkg.split(".")[1]), "api")[0];
      } else if (pkg.includes("mockmacro")) {
        libPath = findDirsIncludes(path.join(docPath, "libs", lib.name, "unittest_testmacro"), "api", "mockmacro")[0];
      } else if (pkg.includes("testmacro")) {
        libPath = findDirsIncludes(path.join(docPath, "libs", lib.name, "unittest_testmacro"), "api", "testmacro")[0];
      } 
    } 
    if (!libPath) libPath = findDirsIncludes(path.join(docPath, "libs", lib.name, pkgName), "api")[0];

    if (!libPath) {
      console.log(`${lib.name}.${pkg} no api found.`)
      continue
    }
    const files = fs.readdirSync(libPath);
    let content = `package ${lib.name}.${pkg}\n\n`

    console.log(`${lib.name}.${pkg} processing...`)
    for (let file of files) {
      let type = file.match(/([0-9a-zA-Z]+)(?=\.html)/)[0]
      content += `// ---------------------------\n// -----${type}\n// ---------------------------\n`
      const $ = cheerio.load(fs.readFileSync(path.join(libPath, file), "utf8"));
      const main = $("#content");      
      if (type.includes("macro")) {
        // 搜索宏定义
        main.find("h2").each((i, elem) => {
          let name = $(elem).find("code").text()
          let intro = nextSibling($, elem, "p").text().trim()
          if (intro.startsWith("功能")) content += generateCJDoc([intro.slice(3).trim()], [], null, null, false) + "\n"
          if (name) content += `public macro ${name}(input: Tokens): Tokens\n\n`
        });
      } else {
        // 搜索文档中的代码片段
        main.find("h2, [id^='extend']:not(#extend)").each((i, elem) => {
          const comments = [] // 注释
          const topDeclNode = nextSibling($, elem, "pre")
          const decls = [] // 顶层声明和子声明
          let name = $(topDeclNode).find("code").text() + "{";
          decls.push(name.substring(0, name.indexOf("{") ?? name.length).replace(/\s+/g, " ").trim())
  
          // 获取子声明（decls）和注释
          let comment = {
            "intros": [],
            "params": [],
            "return": "",
            "throws": ""
          }
          let pushComment = (comment) => {
            let c = generateCJDoc(comment["intros"],comment["params"], comment["return"], comment["throws"], !isTopDecl)
            comments.push(c)
            isTopDecl = false
            Object.assign(comment, {
              "intros": [],
              "params": [],
              "return": "",
              "throws": ""
            })
          }
          let isTopDecl = true
          $(topDeclNode).nextUntil("h2, [id^='extend']:not(#extend)").each((j, subElem) => {
            if (subElem.tagName === "pre") { // 子声明
              pushComment(comment)
              const decl = $(subElem).find("code.language-cangjie").text().replace(/[\n\r\t]/g, "").replace(/\s+/g, " ").trim()
              if (decl) decls.push(decl)
            } else if(subElem.tagName === "p") { // 注释
              let text = $(subElem).text().replace(/\s+/g, " ").trim()
              // 判断注释类型
              if (text.startsWith("功能")) {
                comment["intros"].push(text.slice(3).trim())
              } else if (text.startsWith("参数")) {
                $(subElem).next("ul").find("li").each((i, elem) => {
                  comment["params"].push($(elem).text().replace(/\s+/g, " ").trim())
                })
              }
              else if (text.startsWith("返回值")) {
                comment["return"] = $($(subElem).next("ul").find("li")[0]).text().replace(/\s+/g, " ").trim()
              }
              else if (text.startsWith("类型")) {
                // comment["return"] = text.slice(3)
              }
              else if (text.startsWith("异常")) {
                comment["throws"] = $($(subElem).next("ul").find("li")[0]).text().replace(/\s+/g, " ").trim()
              } else {
                comment["intros"].push(text)
              }
            }
          });
          pushComment(comment)

          if (name && !content.includes(decls[0])) {
            content += `${comments[0] ? comments[0] + "\n" : ""}${decls[0]}`;
            if (decls.length > 1) {
              content += " {\n"
              for (let i = 1; i < decls.length ; i++) {
                content += comments[i] ? `${comments[i]}\n` : ""
                if (name.includes("enum") && !decls[i].includes(":")) 
                  content += `\t| ${decls[i]}\n`
                else content += `\t${decls[i]}\n`
                if (i != decls.length - 1) {
                  content += "\n"
                }
              }
              content += "}\n"
            }
            else if (name.includes("class") || name.includes("struct") || name.includes("interface") || name.includes("extend")) {
              content += " {}\n";
            } else {
              content += "\n"
            }
            content += "\n";
          }
        });
      }
      content += "\n"
    }
    console.log(`${lib.name}.${pkg} done.`)
    if (!fs.existsSync(`./${targetDir}/${lib.name}`)) {
      fs.mkdirSync(path.join(`./${targetDir}/${lib.name}`), { recursive: true });
    }
    fs.writeFileSync(path.join(`./${targetDir}/${lib.name}`, `${pkg}.cjd`), content, "utf8")
  }
}

console.log(`\nThe Cangjie standard library definitions generated by the script are not entirely accurate. 
Please use Git to compare them with the original library files and manually verify.
Specifically, unittest-related library files should be carefully reviewed.`)